package org.hdl.anima.fronent;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.hdl.anima.Application;
import org.hdl.anima.common.io.BinaryInputArchive;
import org.hdl.anima.common.io.BinaryOutputArchive;
import org.hdl.anima.common.io.Bytes;
import org.hdl.anima.common.io.InputArchive;
import org.hdl.anima.common.io.OutputArchive;
import org.hdl.anima.common.io.UnsafeByteArrayOutputStream;
import org.hdl.anima.handler.RequestMessageFactory;
import org.hdl.anima.message.HandSnakeReq;
import org.hdl.anima.message.HandSnakeResp;
import org.hdl.anima.message.HeartBeatReq;
import org.hdl.anima.message.IMessage;
import org.hdl.anima.message.IPush;
import org.hdl.anima.message.IResponse;
import org.hdl.anima.message.MessageHeader;
import org.hdl.anima.message.MessageIdWrap;
import org.hdl.anima.message.MessageType;
import org.hdl.anima.message.Request;
import org.hdl.anima.message.ResponseBinWrap;
import org.hdl.anima.message.ResponseObjWrap;
import org.hdl.anima.transport.Channel;
import org.hdl.anima.transport.support.AbstractCodec;
import org.hdl.anima.utils.StreamUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author qiuhd
 * @since 2014-2-11
 * @version V1.0.0
 */
public class FronentCodec extends AbstractCodec {

	private static final Logger logger = LoggerFactory.getLogger(FronentCodec.class);
	
	public FronentCodec(Application application) {
		
	}

	@Override
	public Object decode(Channel channel, InputStream is) throws IOException {
		int readable = is.available();
		byte[] header = new byte[Math.min(readable, MessageHeader.LENGTH)];
		is.read(header);
		return decode(channel, is, readable, header);
	}

	protected Object decode(Channel channel, InputStream is, int readable,byte[] header) throws IOException {
		// check length.
		if (readable < MessageHeader.LENGTH) {
			return NEED_MORE_INPUT;
		}

		// get data length.
		int len = Bytes.bytes2int(header, 0);
		checkPayload(channel, len);
		int tt = len + MessageHeader.LENGTH;
		if (readable < tt) {
			return NEED_MORE_INPUT;
		}
		// limit input stream.
		if (readable != tt)
			is = StreamUtils.limitedInputStream(is, len);
		
		MessageHeader msgHeader = new MessageHeader(len,header[4],header[5]);
		
		try {
			return decodeBody(channel, is, msgHeader);
		} finally {
			if (is.available() > 0) {
				try {
					if (logger.isWarnEnabled()) {
						logger.warn("Skip input stream " + is.available());
					}
					StreamUtils.skipUnusedStream(is);
				} catch (IOException e) {
					logger.warn(e.getMessage(), e);
				}
			}
		}
	}

	protected Object decodeBody(Channel channel, InputStream is, MessageHeader header) throws IOException {
		
		MessageType type = MessageType.getType(header.getType());
		
		InputArchive inputArchive = BinaryInputArchive.getArchive(is);
		is.mark(MessageHeader.LENGTH);
		int msgid = inputArchive.readInt();
		
		switch (type) {
		case SYSTEM:
			Request sysReq = null ;
			if (msgid == 1) {
				sysReq = new HandSnakeReq();
			}else if (msgid == 2) {
				sysReq = HeartBeatReq.INSTANCE;
			}
			try {
				sysReq.setId(msgid);
				sysReq.deserialize(inputArchive);
			}catch (Exception e) {
				throw new IOException("Failed to decode body,message type:" + MessageType.SYSTEM + ",message id:" + msgid +"cause:" + e.getMessage(),e);
			}
			return sysReq;
		case REQEUST:
			IMessage msg = RequestMessageFactory.getInstance().createMessage(msgid);
			try {
				if (msg != null) {
					msg.deserialize(inputArchive);
					return msg;
				} else {
					is.reset();
					byte[] b = new byte[header.getLength()];
					is.read(b);
					MessageIdWrap wrap = new MessageIdWrap(msgid, b);
					return wrap;
				}
			} catch (Exception e) {
				throw new IOException("Failed to decode body,message type:"+ MessageType.REQEUST + ",message id:" + msgid+ "cause:" + e.getMessage(), e);
			}
		default:
			throw new IOException("Failed to decode body,cause: message type not match!");
		}
	}

	@Override
	public void encode(Channel channle, OutputStream os, Object message)
			throws IOException {
		UnsafeByteArrayOutputStream outputStream = (UnsafeByteArrayOutputStream)os;
		OutputArchive archive = BinaryOutputArchive.getArchive(outputStream);
		MessageHeader header = new MessageHeader();
		if (message instanceof IResponse) {
			IResponse response = (IResponse)message ;
			if (response instanceof HandSnakeResp) {
				header.setType(MessageType.SYSTEM.getValue());
			}else {
				header.setType(MessageType.RESPONSE.getValue());
			}
			header.serialize(archive);
			response.serialize(archive);
		}else if (message instanceof IPush) {
			IPush push = (IPush)message;
			if (message instanceof IPush) {
				header.setType(MessageType.PUSH.getValue());
			}
			header.serialize(archive);
			push.serialize(archive);
		}else  if (message instanceof ResponseObjWrap){
			ResponseObjWrap respWrap = (ResponseObjWrap)message;
			header.setType(MessageType.RESPONSE.getValue());
			header.setEncrypted(respWrap.getHeader().isEncrypted());
			header.serialize(archive);
			respWrap.getContent().serialize(archive);
		}else  if (message instanceof ResponseBinWrap){
			ResponseBinWrap respWrap = (ResponseBinWrap)message;
			header.setType(MessageType.RESPONSE.getValue());
			header.setEncrypted(respWrap.getHeader().isEncrypted());
			archive.writeBuffer(respWrap.getContent());
		}else{
			throw new IOException("Faild to encode,cause : the message can not serialize. Message type:" + message.getClass().getName());
		}
		
//		byte [] msgArray = outputStream.toByteArray();
//		System.out.println(Bytes.bytes2hex(msgArray));
		//写入消息长度
		int msgLen = outputStream.size() ;
		int msgBodyLen = msgLen - MessageHeader.LENGTH;
		byte [] len = Bytes.int2bytes(msgBodyLen);
		outputStream.write(len, 0, 0, 4);
	}
}
